<html>
    <head>
        <title>背景工作 Background Jobs</title>
    </head>
    <body>
        <script>
            // Background Jobs 后台作业
            
                    /*
                        一、导言

                               后台作业用于将一些要在后台执行的任务排队。

                               你可能需要后台作业有几个原因，下面是几个例子：

                                    1、执行长时间运行的任务而无需用户等待。

                                          例如，用户按下“报告”按钮以启动长时间运行的报告作业。你将此作业添加到队列，并在完成后通过电子邮件将报告结果发送给你的用户。

                                    2、创建重试和持久任务以保证代码将被成功执行。

                                          例如，你可以在后台作业中发送电子邮件以克服临时故障并保证最终会发送。这样用户在发送电子邮件时就不会等待。
                    */

                    /*
                        二、抽象包  Abstraction Package

                               ABP为后台作业提供了一个"抽象模块" 和 "几个实现"。

                               它具有内置/默认实现以及 Hangfire、RabbitMQ和Quartz集成。

                               "Volo.Abp.BackgroundJobs.Abstractions" NuGet包提供了创建后台作业项所需的服务，
                               如果你的模块只依赖于这个包，它可以独立于实际的实现/集成。

                               启动模板中已经默人安装了 "Volo.Abp.BackgroundJobs.Abstractions"
                    */

                    /*
                        三、创建后台作业

                               后台作业是一个实现 IBackgroundJob<TArgs>接口 或 派生自 BackGroundJob<TArgs>类的类。
                               
                               TArgs 是一个简单的普通C#类来存储"作业数据"。

                               此示例用于在后台发送电子邮件：
                                    
                               1、作业参数类 (普通C#类)：
                                                首先,定义一个类来存储后台作业的参数：
                                                namespace MyProject
                                                {
                                                    // 一个C#普通类，来存储"作业数据"：
                                                    public class EmailSendingArgs
                                                    {
                                                        public string RecipientEmailAddress { get; set; }
                                                        public string Subject { get; set; }
                                                        public string Body { get; set; }
                                                    }
                                                } 

                               2、后台作业类 (派生自AsyncBackgroundJob<TArgs>)：

                                             然后创建一个使用EmailSendingArgs对象发送电子邮件的后台作业类：
                                             
                                                using System.Threading.Tasks;
                                                using Volo.Abp.BackgroundJobs;
                                                using Volo.Abp.DependencyInjection;
                                                using Volo.Abp.Emailing;

                                                namespace MyProject
                                                {
                                                    public class EmailSendingJob
                                                        : AsyncBackgroundJob<EmailSendingArgs>, ITransientDependency  // 创房后台作业
                                                        // EmailSendingArgs 自定义参数类型(包含需要处理的Ids集合)
                                                    {
                                                        private readonly IEmailSender _emailSender;

                                                        public EmailSendingJob(IEmailSender emailSender)
                                                        {
                                                            _emailSender = emailSender; // 注入邮件发送服务
                                                        }

                                                        // 重写 ExecuteAsync 方法
                                                        public override async Task ExecuteAsync(EmailSendingArgs args)
                                                        {
                                                            await _emailSender.SendAsync(
                                                                args.EmailAddress,
                                                                args.Subject,
                                                                args.Body
                                                            );
                                                        }
                                                    }
                                                }

                                            此作业仅用 IEmailSender 发送电子邮件 (请参阅电子邮件发送模块文档)。

                                            AsyncBackgroundJob<TArgs> (异步后台作业)用于创建需要"执行异步调用"的作业.
                                            
                                            如果方法不需要执行任何一部调用，你可以从 BackgroundJob<TArgs> 派生。

                                4、异常处理  Exception Handling 

                                        后台作业不应隐藏异常.

                                        如果抛出异常，后台作业会在计算的等待时间后"自动重试"。

                                        仅当你不想为当前参数重新运行后台作业时灿隐藏异常。

                    */

                   

                    /*
                        四、取消后台工作

                                如果你的后台作业是可取消的，那么你可以使用标准的"取消令牌系统" 来获取 CancellationToken， 以在请求是取消你的后台作业。

                                请参考一下使用 ICancellationTokenProvider 获取取消令牌的示例：

                                        using System;
                                        using System.Threading.Tasks;
                                        using Microsoft.Extensions.Logging;
                                        using Volo.Abp.DependencyInjection;
                                        using Volo.Abp.Threading;

                                        namespace MyProject
                                        {
                                            // 后台作业类：实现 ITransientDependency 接口
                                            public class LongRunningJob : AsyncBackgroundJob<LongRunningJobArgs>, ITransientDependency
                                            {
                                                private readonly ICancellationTokenProvider _cancellationTokenProvider;

                                                public LongRunningJob(ICancellationTokenProvider cancellationTokenProvider)
                                                {
                                                    _cancellationTokenProvider = cancellationTokenProvider; 
                                                    // 注入 ICancellationTokenProvider 该服务用于获取当前任务的取消令牌
                                                }

                                                // 异步执行当前后台作业
                                                public override async Task ExecuteAsync(LongRunningJobArgs args)
                                                {
                                                    foreach (var id in args.Ids)
                                                    {
                                                        // 每次迭代前通过 ThrowIfCancellationRequested() 检查是否被取消
                                                        // 若收到取消请求，立即抛出 OperationCanceledException 终止作业
                                                        _cancellationTokenProvider.Token.ThrowIfCancellationRequested();
                                                        
                                                        await ProcessAsync(id); // 根据任务ID 处理任务
                                                    }
                                                }
                                            }
                                        } 
                                            
                                   关键设计特点：
                                   
                                          1、 可取消性设计：

                                                通过标准CancellationToken实现协作式取消
                                                确保长时间任务能被及时中断
                                                
                                          2、任务分段处理：
                                         
                                                将大任务拆分为多个小单元（按id处理）
                                                每个单元处理前检查取消状态，避免处理到一半被终止
                                                
                                          3、ABP后台作业特性：

                                                自动重试机制（默认重试3次）
                                                持久化存储（作业状态在重启后依然保留）
                                                分布式支持（适用于集群环境）
                                                
                                    如果应用程序正在关闭，并且我们不想在后台作业中阻止应用程序关闭，则可能需要取消后台作业。

                                    如果请求取消，此例会引发异常。因此，下次应用程序启动时将重试该作业。

                                    如果你不想这么做，只需要从ExecuteAsync方法返回而不会引发任何异常。

                                    （你可以简单地检查_cancellationTokenProvider.Token.IsCancellationRequested属性）
                                    
                    */

                    /*
                       五、 作业名称  Job Name

                                每个后台作业都有一个名称。

                                作业名称在几个地方使用。RabbitMQ提供程序使用作业名称来确定RabbitMQ队列名称。

                                作业名称由 "作业参数类" 确定。
                                对于上面的 EmailSendingArgs示例，作业名称是 MyProject.EmailSendingArgs(全名，包括命名空间的)。

                                你可以使用 [BackgroundJobName] 属性设置不同的作业名称:

                                            using Volo.Abp.BackgroundJobs;

                                            namespace MyProject
                                            {
                                                [BackgroundJobName("emails")]  // 此特性 设置作业名称为 "emails"
                                                public class EmailSendingArgs
                                                {
                                                    public string EmailAddress { get; set; }
                                                    public string Subject { get; set; }
                                                    public string Body { get; set; }
                                                }
                                            }
                    */

                    /*
                        六、将作业项排队   IBackgroundJobManger(后台作业管理器)  

                               现在，你可以使用 IBackgroundJobManger服务队电子邮件发送作业进行排队：
                                            
                                            // 一个应用服务类
                                            public class RegistrationService : ApplicationService
                                            {
                                                private readonly IBackgroundJobManager _backgroundJobManager;

                                                public RegistrationService(IBackgroundJobManager backgroundJobManager)
                                                {
                                                    _backgroundJobManager = backgroundJobManager; // 注入后台作业管理器
                                                }

                                                // 用户注册逻辑
                                                public async Task RegisterAsync(string userName, string emailAddress, string password)
                                                {
                                                    // 创建一个作业数据对象，直接进入队列排队
                                                    await _backgroundJobManager.EnqueueAsync(
                                                        new EmailSendingArgs 
                                                        {
                                                            EmailAddress = emailAddress,
                                                            Subject = "You've successfully registered!",
                                                            Body = "..."
                                                        }
                                                    );
                                                }
                                            }

                                这里注入了 IBackgroundJobManager 服务并使用其 EnqueueAsync 方法将新作业添加到队列中。


                                ★、Enqueue() 方法获取一些可选参数来控制后台作业：
                                    
                                       1、priority 优先级，用于控制作业项的优先级。

                                            它获取一个 "BackgroundJobPriority枚举"，该枚举具有Low、BelowNornal、Normal、aboveNormal 和 Height 枚举值。

                                       2、delay 延迟，用于在第一次尝试之前等待一段时间 (TimeSpan类型)
                    */

                    /*
                        七、禁用后台作业的执行

                                 你可能 希望为你的应用程序禁用后台作业执行。

                                 如果你想在另一个进程中执行后台作业并为当前进程禁用它，通常需要这样做。

                                 使用AbpBackgroundJobOptions 配置作业执行：

                                        [DependsOn(typeof(AbpBackgroundJobsModule))]
                                        public class MyModule : AbpModule
                                        {
                                            public override void ConfigureServices(ServiceConfigurationContext context)
                                            {
                                                Configure<AbpBackgroundJobOptions>(options =>
                                                {
                                                    options.IsJobExecutionEnabled = false; // 配置禁用模块的后台作业
                                                });
                                            }
                                        }        
                    */


                    /*
                        八、默认后台作业管理器   Default Background Job Manager

                                    ABP 包含了一个简单的 IBackgroundJobManager 实现(实现为以下功能)：
                                      
                                        ①、在单线程中作为 FIFO(First-In First-Out  先进先出原则) 工作。

                                        ②、重试作业执行，知道作业成功运行或超时。记录所有异常

                                                        作业的默认超时时间为 2天。

                                        ③、成功执行后从存储（数据库）中删除作业。
                                                       
                                                        如果超时，它会将其设置为放弃并将其保留在数据库中。

                                        ④、在重试之间越来越多地等待作业。
                                                        
                                                        第一次重试等待1分钟，第二次重试等待2分钟，第三次重试等待4分钟，依此类推。

                                Volo.Abp.BackgroundJobsnuget包包含默认的后台作业管理器，默认安装到启动模板中。


                                1、配置默认后台管理器

                                            在模块类中使用 AbpBackgroundJobWorkerOptions 来配置默认的后台作业管理器。

                                            下面的例子更改的后台作业的超时持续时间：
                                                    
                                                    [DependsOn(typeof(AbpBackgroundJobsModule))]
                                                    public class MyModule : AbpModule
                                                    {
                                                        public override void ConfigureServices(ServiceConfigurationContext context)
                                                        {
                                                            // 配置默认后台作业管理器
                                                            Configure<AbpBackgroundJobWorkerOptions>(options =>
                                                            {
                                                                options.DefaultTimeout = 864000; // 设置默认超时时间，(单位毫秒)
                                                            });
                                                        }
                                                    }
                                            
                                            ServiceConfigurationContext选项类中的选项列表如下：

                                                    ①、JobPollPeriod ： 用于确定两个作业轮询操作之间的间隔。默认值为5000毫秒（5秒）。

                                                    ②、MaxJobFetchCount ：用于确定在单个轮询操作中获取的最大作业数。默认值为1000。

                                                    ③、DefaultFirstWaitDuration ：用于确定第一次重试前等待的持续时间。默认为60秒。

                                                    ④、DefaultTimeout： 用于确定作业的超时持续时间。默认值为172800秒（2天）。

                                                    ⑤、DefaultWaitFactor： 用于确定增加重试之间等待持续时间的因子。默认值为2.0(等待时间系数，实际等待时间是设置的重试等待时间乘以这个系数)

                                                    ⑥、DistributedLockName： 用于确定要使用的分布式锁名。默认为AbpBackgroundJobWorker。

                                        
                                2、数据存储  IBackgroundJobStore
                                
                                           默认的后台作业管理器需要一个数据存储来"保存" 和 "读取" 后台作业。

                                           它将IBackgroundJobStore定义为存储作业的抽象。

                                           后台作业模块使用各种数据访问提供程序实现 IBackgroundJobStore (具体可以查看 "后台作业模块的文档")。

                                           如果你不想使用这个模块，你应该自己实现 IBackgroundJobStore 接口。


                                        ★、默认情况下，后台作业模块已安装到启动模板中，它根据你的ORM/数据访问选择工作。

                                3、集群部署

                                        默认后台作业管理器与集群环境兼容（应用程序的多个实例同时运行）。
                                        
                                        它使用分布式锁来确保作业一次只在单个应用程序实例中执行。

                                        但是，分布式锁系统默认在进程中工作。这意味着它实际上不是分布式的，除非你配置分布式锁提供程序。

                                        因此，请按照分布式锁文档为你的应用程序配置提供程序，如果它尚未配置。

                                        如果你不想使用分布式锁提供程序，你可以使用以下选项：

                                                1、在除一个应用程序实例之外的所有应用程序实例中停止后台作业管理器（将AbpBackgroundJobOptions.IsJobExecutionEnabled设置为false，如禁用作业执行部分所述），
                                                   因此只有单个实例执行作业（而其他应用程序实例仍然可以排队作业）。

                                                2、在所有应用程序实例中停止后台作业管理器（设置AbpBackgroundJobOptions.IsJobExecutionEnabled为false，如禁用作业执行部分所述），
                                                   并创建一个专用应用程序（可能是在其自己的容器中运行的控制台应用程序或在后台运行的Windows服务）来执行所有后台作业。
                                                   如果你的后台作业消耗大量系统资源（CPU、RAM或磁盘），这可能是一个不错的选择，因此你可以将该后台应用程序部署到专用服务器，
                                                   并且你的后台作业不会影响应用程序的性能。
                    */

                    /*
                        九、集成其他后台作业管理器
                                                                
                                后台作业系统是可扩展的，你可以使用自己的实现或与构建的集成更改默认后台作业管理器。


                                请参阅一些与构建的后台作业管理器替代方案：

                                                1、Hangfire 后台作业管理器

                                                2、RabbitMQ 后台作业管理器

                                                3、Quartz 后台作业管理器
                    */

                


                    




        </script>
    </body>
</html>